package com.googlecode.yoohoo.xmppcore.protocol.parsing;

import java.util.List;

import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;

import com.googlecode.yoohoo.xmppcore.protocol.IError;
import com.googlecode.yoohoo.xmppcore.protocol.XmlLangText;
import com.googlecode.yoohoo.xmppcore.protocol.XmppProtocolException;
import com.googlecode.yoohoo.xmppcore.protocol.stanza.Message;
import com.googlecode.yoohoo.xmppcore.protocol.stanza.Presence;
import com.googlecode.yoohoo.xmppcore.protocol.stanza.error.BadRequest;
import com.googlecode.yoohoo.xmppcore.protocol.stanza.error.StanzaError;
import com.googlecode.yoohoo.xmppcore.protocol.stream.error.StreamError;

public class XmppParsingFactoryTest {
	private IXmppParsingFactory parsingFactory;
	
	@Before
	public void before() {
		parsingFactory = new PredefinedXmppParsingFactory();
	}
	
	@Test
	public void presence() {
		String presenceXml = "<presence/>";
		
		Object obj = parsingFactory.parse(presenceXml);
		Assert.assertTrue(obj instanceof Presence);
		
		Presence presence = (Presence)obj;
		Assert.assertNull(presence.getObject());
		
		presenceXml = "<presence><show>dnd</show></presence>";
		obj = parsingFactory.parse(presenceXml);
		Assert.assertTrue(obj instanceof Presence);
		
		presence = (Presence)obj;
		Assert.assertEquals("dnd", presence.getShow());
		
		presenceXml =
			"<presence xml:lang='en'>" +
				"<show>dnd</show>" +
				"<status>Hello, world!</status>" +
				"<status xml:lang='zh'>哈罗，世界！</status>" +
			"</presence>";
		obj = parsingFactory.parse(presenceXml);
		Assert.assertTrue(obj instanceof Presence);
		
		presence = (Presence)obj;
		Assert.assertEquals("dnd", presence.getShow());
		Assert.assertEquals(2, presence.getStatus().size());
		Assert.assertEquals(new XmlLangText("Hello, world!", null),
				presence.getStatus().get(0));
		Assert.assertEquals(new XmlLangText("哈罗，世界！", "zh"),
				presence.getStatus().get(1));
	}
	
	@Test
	public void message() {
		String messageXml =
			"<message to='romeo@example.net' from='juliet@example.com/balcony' type='chat' xml:lang='en'>" +
				"<subject>Hi</subject>" +
				"<subject xml:lang='zh'>嗨！</subject>" +
				"<body>Hello, world!</body>" +
				"<body xml:lang='zh'>哈罗，世界！</body>" +
			"</message>";
		
		Object obj = parsingFactory.parse(messageXml);
		Assert.assertTrue(obj instanceof Message);
		
		Message message = (Message)obj;
		
		Assert.assertEquals("romeo@example.net", message.getTo().toString());
		Assert.assertEquals("juliet@example.com/balcony", message.getFrom().toString());
		Assert.assertEquals("chat", message.getType());
		Assert.assertEquals("en", message.getXmlLang());
		
		Assert.assertEquals(2, message.getSubjects().size());
		Assert.assertEquals(null, message.getSubjects().get(0).getXmlLang());
		Assert.assertEquals("Hi", message.getSubjects().get(0).getText());
		Assert.assertEquals("zh", message.getSubjects().get(1).getXmlLang());
		Assert.assertEquals("嗨！", message.getSubjects().get(1).getText());
		
		Assert.assertEquals(2, message.getBodies().size());
		Assert.assertEquals(null, message.getBodies().get(0).getXmlLang());
		Assert.assertEquals("Hello, world!", message.getBodies().get(0).getText());
		Assert.assertEquals("zh", message.getBodies().get(1).getXmlLang());
		Assert.assertEquals("哈罗，世界！", message.getBodies().get(1).getText());
	}
	
	@Test
	public void embedded() {
		parsingFactory.registerParser(new MessageDataParserFactory(), new ProtocolRelation(
				new ProtocolKey("message", null), new ProtocolKey("data", "http://jabber.org/protocol/ibb")));
		String dataText =
			"qANQR1DBwU4DX7jmYZnncmUQB/9KuKBddzQH+tZ1ZywKK0yHKnq57kWq+RFtQdCJ" +
			"WpdWpR0uQsuJe7+vh3NWn59/gTc5MDlX8dS9p0ovStmNcyLhxVgmqS8ZKhsblVeu" +
			"IpQ0JgavABqibJolc3BKrVtVV1igKiX/N7Pi8RtY1K18toaMDhdEfhBRzO/XB0+P" +
			"AQhYlRjNacGcslkhXqNjK5Va4tuOAPy2n1Q8UUrHbUd0g+xJ9Bm0G0LZXyvCWyKH" +
			"kuNEHFQiLuCY6Iv0myq6iX6tjuHehZlFSh80b5BVV9tNLwNR5Eqz1klxMhoghJOA";
		String messageXml =
			"<message from='romeo@montague.net/orchard' id='dsw71gj3' to='juliet@capulet.com/balcony'>" +
				"<data xmlns='http://jabber.org/protocol/ibb' seq='0' sid='i781hf64'>" +
					dataText +
				"</data>" +
			"</message>";
		
		Object obj = parsingFactory.parse(messageXml);
		Assert.assertTrue(obj instanceof Message);
		
		Message message = (Message)obj;
		Assert.assertEquals("romeo@montague.net/orchard", message.getFrom().toString());
		Assert.assertEquals("juliet@capulet.com/balcony", message.getTo().toString());
		Assert.assertEquals("dsw71gj3", message.getId());
		
		Assert.assertNotNull(message.getObject());
		Assert.assertTrue(message.getObject() instanceof MessageData);
		MessageData data = (MessageData)message.getObject();
		Assert.assertEquals("i781hf64", data.getSid());
		Assert.assertEquals(0, data.getSeq());
		Assert.assertEquals(dataText, data.getText());
	}
		
	@Test
	public void streamError() {
		parsingFactory.registerParser(new EscapeYourDataParserFactory(), new ProtocolRelation(
				new ProtocolKey("error", IXmppParsingFactory.NAMESPACE_STREAM),
					new ProtocolKey("escape-your-data", "application-ns")));
		String errorXml =
			"<stream:error>" +
				"<xml-not-well-formed xmlns='urn:ietf:params:xml:ns:xmpp-streams'/>" +
				"<text xml:lang='en' xmlns='urn:ietf:params:xml:ns:xmpp-streams'>" +
					"Some special application diagnostic information!" +
				"</text>" +
				"<escape-your-data xmlns='application-ns'/>" +
			"</stream:error>";

		IError error = (IError)parsingFactory.parse(errorXml);
		Assert.assertNotNull(error);
		Assert.assertTrue(error instanceof StreamError);
		
		Assert.assertNotNull(error.getDefinedCondition());
		Assert.assertEquals("xml-not-well-formed", error.getDefinedCondition());
		Assert.assertNotNull(error.getText());
		Assert.assertEquals("en", error.getText().getXmlLang());
		Assert.assertEquals("Some special application diagnostic information!", error.getText().getText());
		
		Assert.assertTrue(error.getApplicationSpecificCondition() instanceof EscapeYourData);
	}
	
	@Test
	public void stanzaError() {
		parsingFactory.registerParser(new TooManyParametersParserFactory(),
			new ProtocolRelation(new ProtocolKey("error", null),
				new ProtocolKey("too-many-parameters", "application-ns")));
		
		String senderXml =
			"<iq from='roger@e9go.com' to='e9go.com' id='some-id'>" +
				"<test-error xmlns='http://yoohoo.googlecode.com/development/test-error'/>" +
			"</iq>";
		
		String errorXml =
			"<iq type='error' id='some-id'>" +
				senderXml +
				"<error type='modify'>" +
					"<bad-request xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>" +
					"<too-many-parameters xmlns='application-ns'/>" +
				"</error>" +
			"</iq>";

		IError error = (IError)parsingFactory.parse(errorXml);
		Assert.assertNotNull(error);
		Assert.assertTrue(error instanceof StanzaError);
		
		Assert.assertEquals(senderXml, ((StanzaError)error).getSenderXml());
		Assert.assertNotNull(error.getDefinedCondition());
		Assert.assertEquals("bad-request", error.getDefinedCondition());
		Assert.assertNull(error.getText());
		Assert.assertTrue(error.getApplicationSpecificCondition() instanceof TooManyParameters);
		
		errorXml =
			"<iq type='error' id='some-id'>" +
				"<error type='modify'>" +
					"<bad-request xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>" +
					"<too-many-parameters xmlns='application-ns'/>" +
				"</error>" +
			"</iq>";
		
		error = (IError)parsingFactory.parse(errorXml);
		Assert.assertNotNull(error);
		Assert.assertTrue(error instanceof StanzaError);
		
		Assert.assertEquals(null, ((StanzaError)error).getSenderXml());
		Assert.assertNotNull(error.getDefinedCondition());
		Assert.assertEquals("bad-request", error.getDefinedCondition());
		Assert.assertNull(error.getText());
		Assert.assertTrue(error.getApplicationSpecificCondition() instanceof TooManyParameters);
	}
	
	@Test
	public void interference() {
		String interferenceXml =
			"<message to='romeo@example.net' from='juliet@example.com/balcony' type='chat' xml:lang='en'>" +
				"<!-- some comments -->" +
				"<body> There is a white space before me.</body>" +
			"</message>";

		Object obj = null;
		try {
			obj = parsingFactory.parse(interferenceXml);
			Assert.fail();
		} catch (XmppProtocolException e) {
			Assert.assertTrue(e.getError() instanceof BadRequest);
		}
		
		interferenceXml =
			"<message to='romeo@example.net' from='juliet@example.com/balcony' type='chat' xml:lang='en'>" +
				"<body> There is a white space before me.</body>" +
			"</message>";

		obj = parsingFactory.parse(interferenceXml);
		
		Assert.assertTrue(obj instanceof Message);
		Message message = (Message)obj;
		Assert.assertEquals(0, message.getSubjects().size());
		Assert.assertNotNull(message.getBodies());
		Assert.assertNotNull(message.getTo());
		Assert.assertEquals("There is a white space before me.", message.getBodies().get(0).getText());
	}
	
	private class EscapeYourData {}
	
	private class EscapeYourDataParser extends XmppParserAdapter<EscapeYourData> {
		private EscapeYourData escapeYourData;
		@Override
		public EscapeYourData getObject() {
			return escapeYourData;
		}
		
		@Override
		public void processAttributes(String path, List<Attribute> attributes) {
			if (path.indexOf('/') != -1) {
				throw new XmppProtocolException(new BadRequest("Element 'application-ns:escape-your-data' doesn't support child element."));
			}
			
			escapeYourData = new EscapeYourData();
		}
		
		@Override
		public void processText(String path, String text) {
			if (text != null) {
				throw new XmppProtocolException(new BadRequest("Element 'application-ns:escape-your-data' doesn't support text."));
			}
		}
		
	}
	
	private class EscapeYourDataParserFactory implements IXmppParserFactory<EscapeYourData> {

		@Override
		public IXmppParser<EscapeYourData> createParser() {
			return new EscapeYourDataParser();
		}

		@Override
		public ProtocolKey getProtocolKey() {
			return new ProtocolKey("escape-your-data", "application-ns");
		}
		
	}
	
	private class TooManyParameters {}
	
	private class TooManyParametersParser extends XmppParserAdapter<TooManyParameters> {
		private TooManyParameters tooManyParameters;
		@Override
		public TooManyParameters getObject() {
			return tooManyParameters;
		}
		
		@Override
		public void processAttributes(String path, List<Attribute> attributes) {
			if (path.indexOf('/') != -1) {
				throw new XmppProtocolException(new BadRequest("Element 'application-ns:too-many-parameters' doesn't support child element."));
			}
			
			tooManyParameters = new TooManyParameters();
		}
		
		@Override
		public void processText(String path, String text) {
			if (text != null) {
				throw new XmppProtocolException(new BadRequest("Element 'application-ns:too-many-parameters' doesn't support text."));
			}
		}
		
	}
	
	private class TooManyParametersParserFactory implements IXmppParserFactory<TooManyParameters> {

		@Override
		public IXmppParser<TooManyParameters> createParser() {
			return new TooManyParametersParser();
		}

		@Override
		public ProtocolKey getProtocolKey() {
			return new ProtocolKey("too-many-parameters", "application-ns");
		}
		
	}

	private class MessageDataParserFactory implements IXmppParserFactory<MessageData> {

		@Override
		public IXmppParser<MessageData> createParser() {
			return new MessageDataParser();
		}

		@Override
		public ProtocolKey getProtocolKey() {
			return new ProtocolKey("data", "http://jabber.org/protocol/ibb");
		}
		
	}
	
	private class MessageDataParser extends XmppParserAdapter<MessageData> {
		private MessageData messageData;
		
		@Override
		public void processAttributes(String path, List<Attribute> attributes) {
			if (!"data".equals(path))
				throw new RuntimeException("Element 'data' doesn't allow child element.");
			
			messageData = new MessageData();
			
			for (Attribute attribute : attributes) {
				if (attribute.getPrefix() == null && "sid".equals(attribute.getLocalName())) {
					messageData.setSid(attribute.getValue());
				} else if (attribute.getPrefix() == null && "seq".equals(attribute.getLocalName())) {
					int seq;
					try {
						seq = Integer.parseInt(attribute.getValue());
					} catch (Exception e) {
						throw new RuntimeException("Attribute 'seq' of element 'data' isn't a number.");
					}
					messageData.setSeq(seq);
				} else {
					throw new XmppProtocolException(new BadRequest("Invalid attribute name for 'data' element."));
				}				
			}
		}
		
		@Override
		public void processText(String path, String text) {
			messageData.setText(text);
		}
		
		@Override
		public MessageData getObject() {
			return messageData;
		}
		
	}
	
	private class MessageData {
		private int seq;
		private String sid;
		private String text;
		
		public int getSeq() {
			return seq;
		}
		
		public void setSeq(int seq) {
			this.seq = seq;
		}
		
		public String getSid() {
			return sid;
		}
		
		public void setSid(String sid) {
			this.sid = sid;
		}
		
		public String getText() {
			return text;
		}
		
		public void setText(String text) {
			this.text = text;
		}
	}
}
